﻿using System;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using Dsl.Ped.Common;
using Dsl.Ped.Editor.Modules.ErrorArea;
using Dsl.Ped.Editor.Modules.ToolBarArea;
using Dsl.Ped.Editor.Services;
using Dsl.Ped.Editor.Modules.ProjectiveArea;
using Dsl.Ped.Framework.AddIns;
using Dsl.Ped.Framework.Common;
using GalaSoft.MvvmLight.Messaging;

namespace Dsl.Ped.Editor
{
    /// <summary>
    /// View model of the main projectional editor view
    /// </summary>
    public class DslPedEditorViewModel : ViewModelBase
    {
        #region Fields

        /// <summary>
        /// Projective object service used for the handling of the interaction
        /// between the input XML files and projective object. It handles the creation of 
        /// objects of a given type, deserialization of input files into projective objects and the backwards
        /// serialization of the projective objects into the specific XML files
        /// </summary>
        private IProjectiveObjectService projectiveObjectService;

        /// <summary>
        /// Serivce that handles the add-ins available in the editor. It returns the required parameters
        /// of registered add-ins via specialized methods.
        /// </summary>
        private IAddInRegistrationService addInRegistrationService;

        /// <summary>
        /// Current view displaye in the editor. There can be distinct views like Errors, Projective Area, ...
        /// This variable keeps the actual one.
        /// </summary>
        private UserControl contentView;

        /// <summary>
        /// Required information used by the editor like available projection containers, modes, etc..
        /// </summary>
        private EditorResources editorResources;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DslPedEditorViewModel"/> class.
        /// Registration of all required services and messages listens is done in the constructor.
        /// All needed views are instantiated here also.
        /// </summary>
        /// <param name="editorAddIns">The editor add ins</param>
        public DslPedEditorViewModel(IEnumerable<Lazy<IDslPedAddIn, IDslPedAddInMetadata>> editorAddIns)
        {
            // Register all the required services
            RegisterServices(editorAddIns);

            var messenger = new Messenger();
            this.editorResources = new EditorResources(messenger);

            ToolBarAreaView = new ToolBarAreaView(editorResources);
            ProjectiveAreaView = new ProjectiveAreaView(messenger, editorResources);
            ErrorAreaView = new ErrorAreaView(() =>
                {
                    projectiveObjectService.ReloadFromFile();
                    SetProjectiveObject(projectiveObjectService.CurrentProjectiveObject);
                });
            this.contentView = ProjectiveAreaView;
        }

        #endregion

        #region Members

        /// <summary>
        /// Gets or sets a view of the projective area
        /// </summary>
        public ProjectiveAreaView ProjectiveAreaView { get; set; }

        /// <summary>
        /// Gets or sets a view of error area
        /// </summary>
        public ErrorAreaView ErrorAreaView { get; set; }

        /// <summary>
        /// Gets or sets a view of toolbar area
        /// </summary>
        public ToolBarAreaView ToolBarAreaView { get; set; }

        /// <summary>
        /// Gets or sets current content view
        /// </summary>
        public UserControl ContentView
        {
            get
            {
                return contentView;
            }
            set
            {
                contentView = value;
                RaisePropertyChanged(() => ContentView);
            }
        }

        /// <summary>
        /// Gets the dictionary of registered types (namespace to projective type dictionary)
        /// </summary>
        public Dictionary<string, Type> RegisteredTypes
        {
            get { return addInRegistrationService.GetRegisteredTypes(); }
        }

        /// <summary>
        /// Gets the current file name
        /// </summary>
        public string CurrentFileName
        {
            get { return projectiveObjectService.CurrentFileName; }
        }

        /// <summary>
        /// Gets the current projective type
        /// </summary>
        public Type CurrentProjectiveType
        {
            get { return projectiveObjectService.CurrentProjectiveType; }
        }

        /// <summary>
        /// Gets or sets the indication if the projective object was changed in the editor, if so,
        /// this information is used for the display of a * on the top of the file.
        /// </summary>
        public bool IsDirty
        {
            get { return ProjectiveAreaView.IsDirty; }
            set { ProjectiveAreaView.IsDirty = value; }
        }

        #endregion

        #region Methods
        
        /// <summary>
        /// Loads the file of a given name
        /// </summary>
        /// <param name="fileName">Name of the file to be loaded</param>
        /// <returns>Projective object which the file was loaded to, Null if the process failed</returns>
        public object LoadFile(string fileName)
        {
            var result = projectiveObjectService.LoadFromFile(fileName);
            if (result == true)
            {
                return projectiveObjectService.CurrentProjectiveObject;
            }

            return null;
        }

        /// <summary>
        /// Loads the file of a given <see cref="fileName"/>, use the <see cref="XmlReader"/> for the
        /// deserialization of an file into the projective object
        /// </summary>
        /// <param name="fileName">Name of the file to be loaded</param>
        /// <param name="xmlReader"><see cref="XmlReader"/> used for the deserialization</param>
        /// <returns>Projective Object which the file was deserialized to, Null if the process failed</returns>
        public object LoadFile(string fileName, XmlReader xmlReader)
        {
            var result = projectiveObjectService.LoadFromFile(fileName, xmlReader);
            if (result == true)
            {
                return projectiveObjectService.CurrentProjectiveObject;
            }

            return null;
        }

        /// <summary>
        /// Open already loaded file
        /// </summary>
        public void OpenLoadedFile()
        {
            SetProjectiveObject(projectiveObjectService.CurrentProjectiveObject);
            SetEditorResources(projectiveObjectService.CurrentProjectiveObject,
                               projectiveObjectService.CurrentXmlNamespace);
        }

        /// <summary>
        /// Loads and open a file of a given <see cref="fileName"/>
        /// </summary>
        /// <param name="fileName">Name of the file to be loaded</param>
        public void LoadAndOpenFile(string fileName)
        {
            try
            {
                this.LoadFile(fileName);

                this.OpenLoadedFile();
            } 
            catch (Exception exc)
            {
                ErrorAreaView.ViewModel.SetError(exc);
                ContentView = ErrorAreaView;
            }
        }

        /// <summary>
        /// Loads and open a file of a given name, use the <see cref="XmlReader"/> for the deserialization
        /// of the file.
        /// </summary>
        /// <param name="fileName">Name of the file to be loaded</param>
        /// <param name="xmlReader"><see cref="XmlReader"/> used for the deserialization of the file</param>
        public void LoadAndOpenFile(string fileName, XmlReader xmlReader)
        {
            try
            {
                this.LoadFile(fileName, xmlReader);

                this.OpenLoadedFile();
            }
            catch (Exception exc)
            {
                ErrorAreaView.ViewModel.SetError(exc);
                ContentView = ErrorAreaView;
            }
        }

        /// <summary>
        /// Save currently opened file
        /// </summary>
        /// <returns></returns>
        public bool SaveFile()
        {
            return projectiveObjectService.Store2File();
        }

        /// <summary>
        /// Save currently loaded file under the given <see cref="fileName"/> name
        /// </summary>
        /// <param name="fileName">Name under which the current projective object should be saved</param>
        /// <returns>True if the file was saved successfully</returns>
        public bool SaveFile(string fileName)
        {
            return projectiveObjectService.Store2File(fileName);
        }

        /// <summary>
        /// Creates new projective object of a given projective type
        /// </summary>
        /// <param name="projectiveType">Required projective type of the object</param>
        /// <returns>True, if the process succeed, False otherwise</returns>
        public bool NewProjectiveObject(Type projectiveType)
        {
            projectiveObjectService.CreateNewProjectiveObject(projectiveType);
            SetProjectiveObject(projectiveObjectService.CurrentProjectiveObject);

            // TODO: add handling
            return true;
        }

        /// <summary>
        /// Creates new projective object, used the default instance if not null
        /// </summary>
        /// <param name="projectiveType">Required projective type</param>
        /// <param name="defaultObject">Default object instance to be used for the creation</param>
        /// <returns>True, if the process succeed, False otherwise</returns>
        public bool NewProjectiveObject(Type projectiveType, object defaultObject)
        {
            projectiveObjectService.CreateNewProjectiveObject(projectiveType, defaultObject);
            this.SetProjectiveObject(projectiveObjectService.CurrentProjectiveObject);

            // TODO: add handling
            return true;
        }

        /// <summary>
        /// Serialize current projective object into <see cref="XDocument"/>
        /// </summary>
        /// <returns><see cref="XDocument"/> to which the current projective type was serialized</returns>
        public XDocument SerializeProjectiveObject()
        {
            XmlSerializer serializer = new XmlSerializer(CurrentProjectiveType);
            XDocument documentFromDesignerState = new XDocument();
            using (XmlWriter w = documentFromDesignerState.CreateWriter())
            {
                serializer.Serialize(w, projectiveObjectService.CurrentProjectiveObject);
            }

            return documentFromDesignerState;
        }

        /// <summary>
        /// Set the current projective object to the one provided in parameter
        /// </summary>
        /// <param name="projectiveObject">Projective object to be set as current</param>
        private void SetProjectiveObject(IProjective projectiveObject)
        {
            ProjectiveAreaView.ViewModel.SetProjectiveObject(projectiveObject);
            ContentView = ProjectiveAreaView;
        }

        /// <summary>
        /// Set the ersources of the editor
        /// </summary>
        /// <param name="projectiveObject">Projective object of the editor</param>
        /// <param name="xmlNamespace">Namespace</param>
        private void SetEditorResources(IProjective projectiveObject, string xmlNamespace)
        {
            editorResources.ViewModes =
                addInRegistrationService.GetRegisteredViewModes(xmlNamespace);
        }

        /// <summary>
        /// Register <see cref="AddInRegistrationService"/> and <see cref="ProjectiveObjectService"/> for the given
        /// colleciton of AddIns
        /// </summary>
        /// <param name="editorAddIns">Collection of AddIns</param>
        private void RegisterServices(IEnumerable<Lazy<IDslPedAddIn, IDslPedAddInMetadata>> editorAddIns)
        {
            addInRegistrationService = new AddInRegistrationService(editorAddIns);
            projectiveObjectService = new ProjectiveObjectService(addInRegistrationService);
        }

        #endregion
    }
}
